#include<iostream>
#include<vector>
#include<string>
#include<set>
#include<deque>
#include<fstream>
#include<ctime>
#include<boost/regex.hpp>
#include<boost/thread/mutex.hpp>
#include<boost/thread/condition.hpp>
#ifdef WIN32
#include<time.h>
#else
#include<sys/time.h>
#endif
#include"sproxy.hpp"
#include"freeip.hpp"
#include"http.hpp"

#include<boost/date_time/posix_time/posix_time.hpp>

using namespace std;

namespace sproxys {
  deque<string> proxys;
  set<string> aproxys;
  boost::mutex proxys_mutex;
  boost::condition proxys_cond;
  vector<FetcherConf> fetcherconfs;
  vector<ProxyCheckResult> results;
  boost::mutex results_mutex;
  int checker_timeout, fetcher_timeout, verbose, ncheckers;
  string genpac, pacfile;
  string testurl, teststr;
}

bool ProxyCheckResult::operator<(const ProxyCheckResult& r)const {
  if (succ != r.succ) return succ > r.succ;
  if (t != r.t) return t < r.t;
  return proxy < r.proxy;
}

int ProxyChecker::do_check(const string&proxy) {
  ua.set_proxy(proxy);
  if (!ua.has_proxy()) return -1000;
  int r = ua.request(resp, req);
  if (r == -1) {
    return -100;
  }
  if (!resp.is_success()) return -10;
  string::size_type np = resp.content.find(teststr);
  if (np == string::npos){
    string tmp;
    ua.fetch(resp, tmp); //fetch last chance
    if (!tmp.empty()) resp.content += tmp;
    np = resp.content.find(teststr);
    if (np == string::npos)
      return -1;//content fail
  }
  return 1;//success
}

int ProxyChecker::check(const string& proxy, ProxyCheckResult &r) {
  using namespace sproxys;
  using namespace boost::posix_time;
  ptime bgt(microsec_clock::universal_time());
  int rt =-1000;
  try {
    rt = do_check(proxy);
  } catch(exception& e) {
    if(verbose > 5)
      cerr<<"do_check error: "<<e.what()<<endl;
  }
  ptime edt(microsec_clock::universal_time());
  time_duration td(edt-bgt);
  double t = td.total_microseconds();
  t/=1000000;
  r.succ=(rt>0);
  if (r.succ) {
    r.proxy=proxy;
    r.t=t;
    if (verbose > 1)
      cerr<<"proxy "<<proxy<<" success."<<endl;
  } else if (verbose > 5) {
    cerr<<"proxy "<<proxy<<" failed."<<endl;
  }
  return r.succ;
}

int ProxysFetcher::fetch(const FetcherConf& conf) {
  using namespace sproxys;
  Url url(conf.url);
  string cont;
  boost::regex reg(conf.pat);
  if (url.scheme=="http") {
    HttpAgent ua(timeout);
    HttpResponse resp;
    if (conf.postdata.size()) {
      HttpRequestPost req(url);
      req.add_cont(conf.postdata);
      ua.request(resp, req);
    } else {
      HttpRequest req(url);
      ua.request(resp, req);
    }
    if (resp.is_success()) {
      cont = resp.content;
    } else {
      if (verbose > 0) cerr<<"error fetch http "<<conf.url<<endl;
      return -10;
    }
  } else if (url.scheme=="file") {
    cont=conf.url.substr(7);//url.host+url.path;
    ifstream ifs(cont.c_str());
    if(!ifs.good()) {
      if (verbose>0) cerr<<"error reading file "<<cont<<endl;
      return -10;
    }
    cont.clear();
    string tmp;
    while(ifs.good()) {
      getline(ifs, tmp);
      cont+=tmp+"\n";
    }
  }
  cout<<"got content "<<cont.size()<<" from "<<conf.url<<endl;
  vector<string> lps;
  boost::sregex_iterator it(cont.begin(), cont.end(), reg), end;
  while (it!=end) {
    lps.push_back(it->format(conf.tpl));
    ++it;
  }
  if (lps.size()) {
    using namespace sproxys;
    boost::mutex::scoped_lock lock(proxys_mutex);
    for(vector<string>::const_iterator it=lps.begin(); it!=lps.end();++it) {
      if (aproxys.find(*it) == aproxys.end()) {
        aproxys.insert(*it);
        proxys.push_back(*it);
        if (verbose> 10) cerr<<"add proxy "<<*it<<endl;
        if (proxys.size() == 1) {
          proxys_cond.notify_all();
        }
      }
    }
  }
  return 1;
}

ostream& operator<<(ostream &os, const ProxyCheckResult&res) {
  return os<<res.succ<<" "<<res.t<<" "<<res.proxy;
}

void fetch_proxys(void)
{
  using namespace sproxys;
  ProxysFetcher fetcher(fetcher_timeout);
  for (unsigned i=0; i<fetcherconfs.size();++i) {
    try {
      fetcher.fetch(fetcherconfs[i]);
    } catch(exception& e) {
      cerr<<"fetch "<<fetcherconfs[i].url<<" error:"<<e.what()<<endl;
      perror("Unknow");
    } catch(...) {
      cerr<<"fetch "<<fetcherconfs[i].url<<" general error."<<endl;
    }
  }
  {
    boost::mutex::scoped_lock lk(proxys_mutex);
    proxys.push_back("end");
    if (proxys.size() == 1) {
      proxys_cond.notify_all();
    }
  }
}

void check_proxys(void)
{
  using namespace sproxys;
  string proxy;
  ProxyChecker checker(testurl, teststr, checker_timeout);
  ProxyCheckResult result;
  vector<ProxyCheckResult> lresults;
  while (1) {
    {
      boost::mutex::scoped_lock lk(proxys_mutex);
      while (proxys.size() == 0) {
        proxys_cond.wait(lk);
      }
      proxy = proxys.front();
      if (proxy != "end") proxys.pop_front();
      else break;
    }
    try {
      TcpHost host(Url(proxy).host, 80);
      if (is_freeip(host)) {
        if (checker.check(proxy, result)) {//only log succ?
          lresults.push_back(result);
        }
      } else {
        if (verbose>8) cerr<<"nonfree proxy "<<proxy<<endl;
      }
    } catch(exception& e) {
      cerr<<"error when check proxy:"<<proxy<<", exception "<<e.what()<<endl;
    }
  }
  if (lresults.size()) {
    boost::mutex::scoped_lock lk(results_mutex);
    //copy(lresults.begin(), lresults.end(), back_inserter(results));
    results.insert(results.end(), lresults.begin(), lresults.end());
  }
}

ostream& operator<<(ostream&os, const FetcherConf&conf) {
  return os<<"{"<<conf.url<<" "<<conf.pat<<" "<<conf.tpl<<" "<<conf.postdata<<"}";
}
